package com.ailanyin.admin.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.UUID;
import com.ailanyin.admin.service.SysBlacklistUserService;
import com.ailanyin.admin.service.SysLoginService;
import com.ailanyin.admin.service.SysUserService;
import com.ailanyin.admin.vo.LoginBodyVo;
import com.ailanyin.common.constant.UserConstants;
import com.ailanyin.common.exception.Asserts;
import com.ailanyin.common.result.AjaxResult;
import com.ailanyin.common.service.RedisService;
import com.ailanyin.common.utils.CaptchaUtil;
import com.ailanyin.common.utils.EmptyUtil;
import com.ailanyin.common.utils.IpUtil;
import com.ailanyin.mapper.SysUserMapper;
import com.ailanyin.model.SysBlacklistUser;
import com.ailanyin.model.SysUser;
import com.ailanyin.security.service.SecurityUserService;
import com.ailanyin.security.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Date;

/**
 * @author ailanyin
 * @version 1.0
 * @since 2021/10/15 0015 下午 15:55
 */
@Service
public class SysLoginServiceImpl implements SysLoginService {

    @Autowired
    private SecurityUserService securityUserService;
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysBlacklistUserService sysBlacklistUserService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private RedisService redisService;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private HttpServletRequest request;

    @Resource
    private SysUserMapper userMapper;

    @Value("${redis.database}")
    private String database;



    @Override
    public AjaxResult login(String username, String password, String code, String uuid) {
        String ip = IpUtil.getIpAddr(request);
        //黑名单开关
        boolean checkBlacklistOnOffResult = checkBlacklistOnOff();
        if (checkBlacklistOnOffResult) {
            //查看登录ip是否被拉黑
            checkBlacklist(ip);
        }
        //验证码
        checkCaptcha(code, uuid);
        //用户名查询用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        //验证密码
        boolean checkPasswordResult = checkPassword(password, userDetails);
        //密码错误
        if (!checkPasswordResult) {
            securityUserService.delUserDetailsFromMap(username);
            //黑名单
            if (checkBlacklistOnOffResult) {
                return AjaxResult.error(handleBlacklist(ip));
            }
            return AjaxResult.error("账号或密码错误！");
        }
        //密码正确 删除错误记录
        if (checkBlacklistOnOffResult) {
            deleteBlacklistUserFromRedis(ip);
        }
        // update this user last login ip and time
        updateUserLastLoginIpAndTime(userDetails.getUsername(),ip);
        AjaxResult ajax = AjaxResult.success();
        ajax.put("token", jwtTokenUtil.generateToken(userDetails));
        return ajax;
    }

    private void updateUserLastLoginIpAndTime(String username, String ip) {
        userMapper.updateUserLastLoginIpAndTime(username,ip,new Date());
    }


    @Override
    public AjaxResult getCaptchaImage() {

        AjaxResult ajax = AjaxResult.success();

        if (!getRegisterConfig()) {
            ajax.put("register", false);
        }
        if (!getCaptchaConfig()) {
            ajax.put("captchaOnOff", false);
            return ajax;
        }
        Object[] image = CaptchaUtil.createImage();
        // 转换流信息写出
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        try (os) {
            ImageIO.write((BufferedImage) image[1], "jpg", os);
        } catch (IOException e) {
            return AjaxResult.error(e.getMessage());
        }

        Object uuid = UUID.randomUUID().toString(true);
        ajax.put("uuid", uuid);
        ajax.put("img", Base64.encode(os.toByteArray()));
        redisService.set(database + UserConstants.CAPTCHA_KEY + uuid, image[0], 120);
        return ajax;
    }

    @Override
    public void register(LoginBodyVo register) {

        // 检查是否开启注册
        if (!getRegisterConfig()) {
            Asserts.fail("系统暂未开启注册！");
        }
        // 检查验证码
        checkCaptcha(register.getCode(),register.getUuid());
        // 新建用户
        SysUser user = new SysUser();
        user.setUserName(register.getUsername());
        user.setNickName(register.getUsername());
        user.setPassword(register.getPassword());
        sysUserService.insertUser(user);
    }

    private boolean getCaptchaConfig() {
        try {
            return UserConstants.CAPTCHA_ON.equals(redisService.get(database + UserConstants.CAPTCHA_ON_OFF_KEY));
        } catch (Exception e) {
            Asserts.fail("Redis中验证码配置不存在！");
            return false;
        }
    }

    private boolean getRegisterConfig() {
        try {
            return UserConstants.REGISTER_ON.equals(redisService.get(database + UserConstants.REGISTER_ON_OFF_KEY));
        } catch (Exception e) {
            Asserts.fail("Redis中注册配置不存在！");
            return false;
        }
    }

    private boolean checkBlacklistOnOff() {
        try {
            return UserConstants.BLACKLIST_ON.equals(redisService.get(database + UserConstants.BLACKLIST_ON_OFF_KEY));
        } catch (Exception e) {
            Asserts.fail("Redis中黑名单配置不存在！");
            return false;
        }
    }

    private void checkBlacklist(String ip) {
        if (redisService.sIsMember(database + UserConstants.BLACKLIST_KEY, ip)) {
            Asserts.fail("IP:["+ip+"]已经被拉黑，请联系管理员！");
        }
    }

    private void checkCaptcha(String code, String uuid) {
        if (getCaptchaConfig()) {
            if (EmptyUtil.isEmpty(code) || EmptyUtil.isEmpty(uuid)) {
                Asserts.fail("验证码或uuid获取失败！");
            }
            try {
                if (!redisService.hasKey(database + UserConstants.CAPTCHA_KEY + uuid)) {
                    Asserts.fail("验证码已经过期失效！");
                } else {
                    if (!code.equalsIgnoreCase(redisService.get(database + UserConstants.CAPTCHA_KEY + uuid).toString())) {
                        Asserts.fail("验证码输入错误！");
                    }
                }

            } finally {
                redisService.del(database + UserConstants.CAPTCHA_KEY + uuid);
            }
        }
    }

    private boolean checkPassword(String password, UserDetails userDetails) {
        return passwordEncoder.matches(password, userDetails.getPassword());
    }

    private String handleBlacklist(String ip) {
        int errorCount;
        StringBuilder builder = new StringBuilder();
        try {
            //半小时过期时间
            errorCount = (int) redisService.hGet(database + UserConstants.LOGIN_ERROR_COUNT, ip);
            ++errorCount;
            if (errorCount < UserConstants.MAX_ERROR_COUNT) {
                redisService.hSet(database + UserConstants.LOGIN_ERROR_COUNT, ip, errorCount, 1800);
                builder.append("您还有")
                        .append(UserConstants.MAX_ERROR_COUNT - errorCount)
                        .append("次输错密码的机会！");
                return builder.toString();
            } else {
                // 黑名单进Redis
                redisService.sAdd(database + UserConstants.BLACKLIST_KEY, ip);
                // 黑名单进Mysql
                addBlacklistUser(ip);
                return UserConstants.HAS_BEEN_PULLED_BLACK;
            }

        } catch (Exception e) {
            redisService.hSet(database + UserConstants.LOGIN_ERROR_COUNT, ip, 1, 1800);
            builder.append("您还有")
                    .append(UserConstants.MAX_ERROR_COUNT - 1)
                    .append("次输错密码的机会！");
            return builder.toString();
        }
    }

    private void deleteBlacklistUserFromRedis(String ip) {
        redisService.hDel(database + UserConstants.LOGIN_ERROR_COUNT, ip);
    }

    private void addBlacklistUser(String ip) {
        SysBlacklistUser blacklistUser = new SysBlacklistUser();
        blacklistUser.setIp(ip);
        // "1" == "错误密码次数过多"
        blacklistUser.setReason("1");
        sysBlacklistUserService.insertBlacklistUser(blacklistUser);
    }
}
